package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"log/slog"
	"os"
	"strconv"
	"time"

	"github.com/redis/go-redis/v9"
)

/*
redis-cli    就进入到redis界面

set name 大乔乔
get name

set age  18    //表面看18是个整数 实际存进去了之后会转成字符串
get age //得到 "18"


*/

type Student struct {
	Id   int
	Name string
}

// 我们连接的是0号库  0号库上面链接各种业务  比如用户id  商品id等 为了防止冲突就加了前缀
func WriteStudent2Redis(client *redis.Client, stu *Student) error {
	if stu == nil {
		return nil
	}
	key := "'STU_" + strconv.Itoa(stu.Id) //避免各种id冲突，用前缀区分
	v, err := json.Marshal(stu)
	if err != nil {
		return err
	}
	err = client.Set(context.Background(), key, string(v), 5*time.Minute).Err()
	return err
}

func GetstudentFromedis(client *redis.Client, sid int) *Student {

	key := "'STU_" + strconv.Itoa(sid) //上/避免各种id冲突，用前缀区分
	v, err := client.Get(context.Background(), key).Result()
	if err != nil {
		if err != redis.Nil { //如果key不存在，会返回redis.Nil
			log.Println(err)

		}
		return nil
	}
	var stu Student
	err = json.Unmarshal([]byte(v), &stu)
	if err != nil {
		log.Println(err)
		return nil
	}
	return &stu
}

func stringValue(ctx context.Context, client *redis.Client) {
	key := "name"
	value := "大乔乔"
	err := client.Set(ctx, key, value, 1*time.Second).Err() //1秒后失效。0表示永不失效
	checkError(err)

	client.Expire(ctx, key, 3*time.Second) //通过Expire设置3秒后失效
	time.Sleep(2 * time.Second)
	//Get返回的是*redis.StringCmd 不是一个真正的可用类型 Result才转成真正可用的类型
	v2, err := client.Get(ctx, key).Result()
	checkError(err)
	fmt.Println(v2)

	err = client.Set(ctx, "age", 18, time.Second).Err()
	checkError(err)

	v3, err := client.Get(ctx, "age").Int()
	checkError(err)
	fmt.Println("age =%d\n", v3)

	//删除key
	n, err := client.Del(ctx, "not_exissts").Result() //n 就是删除几个key
	if err == nil {
		fmt.Printf("删除%d个key\n", n)
	}

}

// value 是list   天然保持顺序
func listValue(ctx context.Context, client *redis.Client) {
	key := "ids"
	defer client.Del(ctx, key)
	values := []interface{}{1, "中", 3, 3, 4}       //切片元素类型有 各种类型混合
	err := client.RPush(ctx, key, values...).Err() //RPush向List右侧插入。如果List不存在会先创建 R就是Right Left LPush
	checkError(err)

	v2, err := client.LRange(ctx, key, 0, -1).Result()
	//截取。0 -1 双闭区间 -1就是最后一个元素 LRange L表示List Range及遍历List 0表示第一个 -1表示最后一个
	//返回的都是字符串 尤其数字
	checkError(err)
	fmt.Println(v2)

}

// set 就是没有重复数据 也不管顺序
func SetValue(ctx context.Context, client *redis.Client) {
	key := "ids"
	defer client.Del(ctx, key)
	values := []interface{}{1, "中", 3, 4, 3, 1}
	err := client.SAdd(ctx, key, values...).Err() //SAdd向set中添加元素，set里不允许出现重复元
	//素  SAdd   S表示set     values是不定长参数

	checkError(err)

	//判断Set中是否包含指定元素
	var value any
	value = 1                                    //数字1会转成string再去redis里查找
	if client.SIsMember(ctx, key, value).Val() { //key    value是要查找的元素
		//SIsMember  S表示set  IsMember 是否是某个集合的成员
		fmt.Printf("set中包含%#V\n", value)
	} else {
		fmt.Printf("Set中不包含%#v\n", value)
	}
	value = "1"
	if client.SIsMember(ctx, key, value).Val() {
		fmt.Printf("set中包含%#V\n", value)
	} else {
		fmt.Printf("Set中不包含%#v\n", value)
	}

	value = 2
	if client.SIsMember(ctx, key, value).Val() {
		fmt.Printf("Set中包含%#v\n", value)
	} else {

		fmt.Printf("Set中不包含%#V\n", value)

	}

	//遍历Set
	for _, ele := range client.SMembers(ctx, key).Val() { //SMembers 获得set里面所有成员
		fmt.Println(ele)
	}

	key2 := "ids2"
	defer client.Del(ctx, key2)
	values = []interface{}{1, "中“,“大", "乔"}
	err = client.SAdd(ctx, key2, values...).Err() //SAdd向Set中添加元素
	checkError(err)

	//差集
	fmt.Println("key - keyz 差集")
	for _, ele := range client.SDiff(ctx, key, key2).Val() { //key - keyz
		fmt.Println(ele)
	}

	fmt.Println("keyz - key 差集")
	for _, ele := range client.SDiff(ctx, key2, key).Val() {
		fmt.Println(ele)
	}

	//交集
	fmt.Println("key & keyz I#")
	for _, ele := range client.SInter(ctx, key, key2).Val() {
		fmt.Println(ele)
	}

}


// value是ZSet（有序的Set 元素不重复） 如何保证的


// type Z struct {
//     Score  float64 //用 Score进行排序
//     Member interface{} //Member可以是任意类型
func Zsetvalue (ctx context.Context, client *redis. Client){
	key := "ids"
	defer client.Del(ctx, key)
	values := []redis.Z{{Member:"张三",Score:70.0},
	{Member:"李四",Score:100.0},
	{Member:"王五",Score:80.0}}//Score是用来排序的，比如把时间戳赋给score 那么就可以按照时间进行排序了

	err := client.ZAdd(ctx, key, values...) .Err()
	checkError(err)
	//遍历ZSet，按Score有序输出Member
	//遍历ZSet，按Score有序输出Member
	for _, ele := range client.ZRange(ctx, key, 8,-1).Val(){
		fmt. Println (ele)
	}
}



func hashtableValue(ctx context.Context, client *redis.Client) {
	//HMSet    H表示hash表结构hashTable  M 表示map  
	err := client.HMSet(ctx, "学生1", map[string]interface{}{"Name": "张三", "Age": 18, "Height": 173.5}).Err()
	checkError(err)
	err = client.HMSet(ctx, "学生2", map[string]interface{}{"Name": "李四", "Age": 20, "Height": 180.0}).Err()
	checkError(err)

	age, err := client.HGet(ctx, "学生2", "Age").Result()//获取学生2 对应的年龄
	checkError(err)
	fmt.Println(age)

	for field, value := range client.HGetAll(ctx, "学生1").Val() { //HGetAll 获取所有完整map
		fmt.Println(field, value) //value都是字符串
	}

	// client.Del(ctx, "学生1")
	// client.Del(ctx, "学生2")
}



// 遍历key。直接用keys命令是全库遍历，redis是单线程的，会阻塞很长时间。
//
// SCAN・cursor・［MATCH pattern］・［COUNT・count］。scan命令注意事項： 

// 第一次遍历cursor 传0就可以了 第二次了 根据第一次的返回值返回查询数据和游标  ［MATCH pattern］就是正则表达式 ［COUNT・count］就是一个批次遍历几个曹位
//
// 1、当游标返回e时，表示迭代结束。第一次 scan 时指定游标为 e，表示开启新的一轮迭代。cursor是HashTable槽位里的值，并不是递增的。
//
// 2、count表示一次遍历多少个key，这些key可能全部不能匹配patten。count设为1080e比较合适，count越大总耗时越短，但是单次查询阻塞的时间越长。
//
// 3、返回的结果可能会有重复，需要客户端去重复，这点非常重要。
//
//  4、遍历的过程中如果有数据修改，改动后的数据能不能遍历到是不确定的。 比如 本来这个key在第6个曹位  刚开始我遍历的时候一个批次遍历3个槽位 刚遍历的时候  这个key在第6个曹位， 这时候 我第一个批次已经遍历过了3个槽位 遍历第二个批次的时候 但是中间其他协程修改了这个key  放在了遍历过的 2号槽位   那么就会找不到这个key 所以可能会漏掉
// 5、单次返回的结果是空的并不意味着遍历结束，而要看返回的游标值是否为0





func scan(ctx context.Context, client *redis. Client) {

if client == nil {
	 fmt.Println("connect redis failed")
	os. Exit (1)
}

	const(
		MID = "_daa_"
	)

for i:= 0; i < 10;i++ {
	//构造10个key，都匹配模式*__dqq_*
	key := strconv. Itoa(i) + MID + strconv.Itoa(i)
	err := client. Set(ctx, key, "1", 0) .Err()

if err != nil {
fmt. Println(err)

}
//删除创建的 key
// defer func ()  {
// 	for i := 0; i < 10; i++ {
// 		key:=strconv.Itoa(i) + MID + strconv.Itoa(i)
// 		client.Del(ctx,key)
// 	}
// }
}


const COUNT = 100//遍的批次大小，建以/10000
var cursor uint64 =0
dup := make(map[string]struct{},COUNT)//对遍历出来的key排重
for {
//取出所有match上patten的key。如果match参数设为空，则是遍历库里的所有key
	Keys,c, err = client.Scan(ctx, cursor, "*"*MID+"", COUNT) . Result()
	if err != nil {
		fmt. Printin(err) 
		break
	}
	fmt.Printf("cursor %d keys count %d\n"',c,len(keys))
    for _, key := range Keys {
		if _, ok := dup[key]; ok {
			continue
		}
		fmt.Println(key)
		dup[key] = struct{}{}
	}
}


}












func main() {
	client := redis.NewClient(&redis.Options{
		Addr:     "127.0.0.1:6379",
		Password: "", //没有密码
		DB:       0,  //redis默认会创建0-15号DB，这里使用默认的DB 默认会划分成16个库 这里使用几号库
		/*Username: ,    一般都没有设置用户名密码
		Password: ,

		*/

	})
	// NewClient 之后并不意味着链接就建立好了，能ping成功才说明链接成功
	//  延迟到ping的时候 或者 延迟到具体读写请求的时候
	if err := client.Ping(context.Background()).Err(); err != nil {
		slog.Error("连接到redis失败", "error", err)
	} else {
		slog.Info("连接到redis成功")
	}

	ctx := context.TODO()
	stringValue(ctx, client)
	listValue(ctx, client)
	hashtableValue(ctx, client)
}
func checkError(err error) {
	if err != nil {
		if err == redis.Nil { //Nil 就是不存在
			fmt.Println("key不存在")
		} else {
			fmt.Println(err)
			os.Exit(1)
		}
	}
}

// 先确保启动了Redis服务  sudo service redis-server start
// go run ./redis




